/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

/**
 * 1. MyPromise 是一个类，类中的构造函数接受一个函数作为参数，且该函数在 new 的时候会立即执行
 */

const PENDING = 'Pending';
const FULFILLED = 'FulFilled';
const REJECTED = 'Rejected';

class MyPromise {
    // 类中的构造函数接受一个函数作为参数，且该函数在 new 的时候会立即执行，函数在执行时接收两个参数: resolve 和 reject
    constructor(fn) {
        this.status = PENDING;
        this.resolveValue;
        this.rejectedValue;
        this.successCallbackStack = [];
        this.failCallbackStack = [];
        try {
            fn((value) => this._resolve(value), (value) => this._reject(value));
        }catch(err) {
            this._reject(err);
        }
    }

    // 静态 resolve 方法，相当于 MyPromise 构造方法中的第一个形参, 返回的是一个 Promise
    static resolve(value) {
        if(value instanceof MyPromise) {
            // 2. 如果是 Promise，则直接返回即可
            return value;
        } else {
            // 1, 如果 value 是普通值，返回 promise
            // 返回的是一个 Promise, 只是该 Promise 只有 resolve 状态
            return new MyPromise((resolve, reject) => {
                resolve(value);
            })
        }
    }

    // 静态 reject 方法，相当于 MyPromise 构造方法中的第一个形参, 返回的是一个 Promise
    static reject(value) {
        if(value instanceof MyPromise) {
            // 2. 如果是 Promise，则直接返回即可
            return value;
        } else {
            // 1, 如果 value 是普通值，返回 promise
            // 返回的是一个 Promise, 只是该 Promise 只有 resolve 状态
            return new MyPromise((resolve, reject) => {
                reject(value);
            })
        }
    }

    // 1. 接收的参数不能是undefined/null
    // 2. 按照数组的元素下标顺序返回相对应的值
    // 3. 返回的是个 MyPromise
    // 4. 返回值是个数组
    static all(arg) {
        const result = [];
        return new MyPromise((resolve, reject) => {
            let step = 0;
            // 该方法是保证输出的结果数组的顺序和输入的数组顺序一致
            function addData(key, value) {
                result[key] = value;
                step += 1;
                // 等待执行结束再
                if(step === arg.length) {
                    resolve(result);
                }
            }
            if(!arg) {
                // 接收的参数不能是undefined/null
                reject(new TypeError(`${typeof arg} is not iterable (cannot read property Symbol(Symbol.iterator))`));
                return;
            }
            arg.forEach((item, index) => {
                if(!(item instanceof MyPromise)) {
                    // 如果元素不是 Promise 对象，则直接 push 即可
                    addData(index, item);
                } else {
                    // 元素是 Promise 的对象，则将 resolve 的值 push
                    item.then(val => addData(index, val), reject);
                }
            })
        })
        
    }

    // 1. 接收的参数不能是undefined/null
    // 2. 返回最先执行结束的状态
    // 3. 返回的是个 MyPromise
    static race(arg) {
        return new MyPromise((resolve, reject) => {
            // 返回最先执行结束的状态，默认状态为 false
            let index = false;
            function setResolveStatus(value) {
                if(!index) {
                    index = true;
                    resolve(value);
                }
            }
            function setRejectStatus(reason) {
                if(!index) {
                    index = true;
                    reject(reason);
                }
            }
            
            arg && arg.forEach(item => {
                if(item instanceof MyPromise) {
                    // 是实例，则去进行状态的获取
                    item.then(val => setResolveStatus(val), (reason) => setRejectStatus(reason));
                }
            })
        })
    }

    // _resolve 方法接收一个参数，当调用 resolve() 方法时，表示当前 Promise 状态由 Pending 变为 fulFulled，且 Promise 状态不可逆。
    _resolve(value) {
        // Promise 状态不可逆
        if(PENDING != this.status) {
            return;
        }
        // 状态由 Pending 变为 fulFulled
        this.status = FULFILLED;
        // resolve 接收的参数作为 then 链式调用时传递的函数的参数
        this.resolveValue = value;
        // 判断当前的成功回调栈中是否有回调,若存在，则执行栈中的所有回调。
        // 特别注意: 此时栈中的回调都是从 then 方法中收集过来的，then 方法返回的是一个 Promise，从开始收集函数到栈中起，其状态一直是 Pending，此处函数执行后，其状态需要为 resolve 或 rejected
        while(this.successCallbackStack.length) {
            this.successCallbackStack.shift()();
        }
    }

    // _reject 方法接收一个参数，当调用 reject() 方法时，表示当前 Promise 状态由 Pending 变为 rejected，且 Promise 状态不可逆。
    _reject(value) {
        // Promise 状态不可逆
        if(PENDING != this.status) {
            return;
        }
        // 状态由 Pending 变为 fulFulled
        this.status = REJECTED;
        // rejected 接收的参数作为 then 链式调用时传递的函数的参数
        this.rejectedValue = value;
        // 判断当前的失败回调栈中是否有回调,若存在，则执行栈中的所有回调
        while(this.failCallbackStack.length) {
            this.failCallbackStack.shift()();
        }
    }

    // then 方法, 接收两个参数。
    // 当前状态为 fulFilled，则执行第一个函数，若是 rejected, 则执行第二个函数。若是 pending,则先将函数收集起来，当状态变为 fulFilled 或者 rejected 时，再执行。
    // 其返回值是一个新的 Promise 对象
    then(successCallback, failCallback) {
        // 两个参数都必须是函数,若不是函数，则用 val => val 替换
        successCallback = (successCallback && typeof successCallback === 'function') ? successCallback : val => val;
        failCallback = (failCallback && typeof failCallback === 'function') ? failCallback : val => {throw val};

        // 返回一个新的 Promise
        let promise1 = new MyPromise((resolve, reject) => {
            if (FULFILLED === this.status) {
                // 当前状态为 fulFilled，则执行第一个函数,函数的返回值会作为下一个 then 的传入函数的参数
                // 为了获取 promise1
                setTimeout(() => {
                    try {
                        const result = successCallback(this.resolveValue);
                        this._resolvePromise(promise1, result, resolve, reject);
                    }catch(err) {
                        reject(new Error(err));
                    }
                }, 0);
            } else if (REJECTED === this.status) {
                // 若是 rejected, 则执行第二个函数,函数的返回值会作为下一个 then 的传入函数的参数
                setTimeout(() => {
                    try {
                        const reason = failCallback(this.rejectedValue);
                        this._resolvePromise(promise1, reason, resolve, reject);
                    }catch(err) {
                        reject(new Error(err));
                    }
                }, 0);
            } else {
                // 若当前 Promise 是 pending,则先将 then 中的形参函数收集起来。
                // 即当前 Promise 的 then 方法返回的也是 Promise 对象，因为 then 方法的形参一直没有执行，所以 then 方法返回的 Promise 的状态一直是 Pending
                // 由于要在 pending 状态切换后执行函数，此处 push 进数组的是一个匿名函数
                this.successCallbackStack.push(() => {
                    setTimeout(() => {
                        try {
                            const result = successCallback(this.resolveValue);
                            this._resolvePromise(promise1, result, resolve, reject);
                        }catch(err) {
                            reject(new Error(err));
                        }
                    }, 0);
                });
                this.failCallbackStack.push(() => {
                    setTimeout(() => {
                        try {
                            const reason = failCallback(this.rejectedValue);
                            this._resolvePromise(promise1, reason, resolve, reject);
                        }catch(err) {
                            reject(new Error(err));
                        }
                    }, 0);
                });
            }
        })

        return promise1;
    }

    // catch 是 then 的第一个参数为 undefined 的另一种写法 
    catch(failCallback) {
        return this.then(undefined, failCallback);
    }

    // 1.无论执行失败还是成功，都会执行该函数
    // 2.返回是一个 Promise
    // 3.将值继续向下传递
    finally(callback) {
        return this.then(value => {
            return MyPromise.resolve(callback()).then(() => value);
        }, reason => {
            return MyPromise.resolve(callback()).then(() => {throw reason});
        })
    }

    // 判断 then 方法中的函数执行结果类型，如果是 Promise 对象，则调用其 then 方法，获取到值并返回；如果是普通值，则直接可以返回
    _resolvePromise(promise1, x, resolve, reject) {
        // 返回值 不允许是 Promise 本身
        if(promise1 === x) {
            reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
        }
        if(x instanceof MyPromise) {
            x.then(resolve, reject);
        } else {
            resolve(x);
        }
    }
}

// 测试1: 执行器报错捕获
// new MyPromise((resolve, reject) => {
//     console.log('---------------------测试 1 开始-----------------------------');
//     throw new Error('执行器报错，捕获报错');
// })
// .then(value => {
//     console.log(value + ': 失败');
//     console.log('---------------------测试 1 结束-------------------------------');
// }, reason => {
//     console.log(reason.message + ': 成功');
//     console.log('---------------------测试 1 结束-------------------------------');
// });

// 测试2：then 中方法执行报错捕获
// new MyPromise((resolve, reject) => {
//     console.log('---------------------测试 2 开始-----------------------------');
//     reject();
//     // resolve();
// })
// .then(value => {
//     throw new Error('then 方法执行报错，捕获报错');
// }, reason => {
//     throw new Error('then 方法执行报错，捕获报错');
// })
// .then(value => {
//     console.log(value + ': 失败');
//     console.log('---------------------测试 2 结束-------------------------------');
// }, reason => {
//     console.log(reason.message + ': 成功');
//     console.log('---------------------测试 2 结束-------------------------------');
// });

// // 测试3： 执行器中包含异步函数或者同步函数
// new MyPromise((resolve, reject) => {
//     // setTimeout(() => {
//     //     resolve('成功');
//     // }, 4000);
//     resolve('成功')
// })
// .then(value => {
//     return value;
// })
// .then(value => {
//     console.log(value)
// }, reason => {
//     console.log(reason.message)
// });

// 测试4： then 方法中返回的函数为Promise
// new MyPromise((resolve, reject) => {
//     resolve(1);
// })
// .then(value => {
//     return new MyPromise((resolve, reject) => {
//         setTimeout(() => {
//             console.log('ha');
//             resolve(value);
//         }, 2000);
//     })
// })
// .then(value => {
//     console.log(value);
// });

// 测试5： then 方法中的参数若不是函数，则忽略
// new MyPromise((resolve, reject) => {
//     // setTimeout(() => {
//     //     resolve('成功');
//     // }, 4000);
//     resolve('成功')
// })
// .then('ha')
// .then(value => {
//     console.log(value)
// }, reason => {
//     console.log(reason.message)
// });

// 测试6： Promise.all
// let p1 = new Promise((resolve, reject) => {
//     setTimeout(() => {
//         resolve('1')
//     }, 4000);
// });
// let p2 = new Promise((resolve, reject) => {
//     resolve('2')
// })
// MyPromise.all([3, p1, p2])
// .then(value => console.log(value))

// 测试7： resolve 的使用
// MyPromise.resolve([3, 4, 7])
// .then(value => console.log(value));
// let p1 = new MyPromise((resolve, reject) => {
//     setTimeout(() => {
//         console.log('延迟执行');
//         resolve('执行结束');
//     }, 4000);
// })
// MyPromise.resolve(p1)
// .then(value => console.log(value));

// 测试8： reject 的使用
// MyPromise.reject([3, 4, 7])
// .then(1, value => console.log(value));
// let p1 = new MyPromise((resolve, reject) => {
//     setTimeout(() => {
//         console.log('延迟执行');
//         reject('执行结束');
//     }, 4000);
// })
// MyPromise.reject(p1)
// .then(1, value => console.log(value));

// 测试9： finally 的使用
// new MyPromise((resolve, reject) => {
//     setTimeout(() => {
//         console.log('延迟执行');
//         resolve('执行结束');
//     }, 4000)
// })
// .finally(() => {
//     return new MyPromise((resolve, reject) => {
//         console.log('这是 finally 函数');
//         setTimeout(() => {
//             resolve('p1');
//         }, 4000)
//     })
// })
// .then(value => console.log(value));

// // 测试10：race
// let p1 = new MyPromise((resolve, reject) => {
//     setTimeout(() => {
//       resolve('success')
//     },1000)
// })
  
// let p2 = new MyPromise((resolve, reject) => {
//     setTimeout(() => {
//       reject('failed')
//     }, 500)
// })
  
// MyPromise.race([p1, p2])
// .then((result) => {
//     console.log(result)
// }, (error) => { 
//     console.log(error)  // 打开的是 'failed'
// });

// 测试11： 失败 catch 捕获错误
// new MyPromise((resolve, reject) => {
//     reject('失败');
// })
// .then((result) => {
//     console.log(result)
// })
// .catch((error) => { 
//     console.log(error)  // 打开的是 'failed'
// })
